package com.alibaba.otter.manager.web.codegen;

import com.google.common.base.CaseFormat;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.reflections.Reflections;
import org.reflections.scanners.Scanners;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.*;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class SimpleApiCodeGenApplication {

    private final String template = "export async function %s(%s){\n" +
            "  const {%s}  = await %s\n" +
            "  return %s;\n" +
            "}";


    private final Map<AnnotatedElement, String> functionName = Map.of(
            GetMapping.class, "httpGet(%s%s)",
            PostMapping.class, "httpPost(%s%s)",
            PutMapping.class, "httpPut(%s%s)",
            PatchMapping.class, "httpPatch(%s%s)",
            DeleteMapping.class, "httpDelete(%s%s)"
    );


    public static void main(String[] args) {
        new SimpleApiCodeGenApplication().generateCode();
    }

    private void generateCode() {

        Reflections reflections = new Reflections(
                new ConfigurationBuilder()
                        .addScanners(Scanners.MethodsAnnotated, Scanners.TypesAnnotated)
                        .setUrls(ClasspathHelper.forPackage("com.alibaba.otter.manager.web.resource"))
                        .forPackages("com.alibaba.otter.manager.web.resource")
        );


        Map<String, StringBuilder> ts = new HashMap<>();
        generateCode(reflections, ts, GetMapping.class);
        generateCode(reflections, ts, PutMapping.class);
        generateCode(reflections, ts, PostMapping.class);
        generateCode(reflections, ts, PatchMapping.class);
        generateCode(reflections, ts, DeleteMapping.class);
        String outDir = "D:/Java/codegen/otter/";
        ts.forEach((k, v) -> {
            try {
                int i = k.indexOf("Resource");
                String name = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, k.substring(0, i)) + "-api/index.ts";
                String fn = outDir + name;
                FileUtils.writeStringToFile(new File(fn), v.toString(), StandardCharsets.UTF_8);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });


    }

    private void generateCode(Reflections reflections, Map<String, StringBuilder> ts, Class<? extends Annotation> annotatedElement) {
        Set<Method> getMappings = reflections.getMethodsAnnotatedWith(annotatedElement);

        for (Method m : getMappings) {
            StringBuilder sb = ts.computeIfAbsent(m.getDeclaringClass().getSimpleName(), k -> new StringBuilder().append("import {httpGet,httpPageableGet,httpPageablePost,httpPost,httpPut,httpDelete} from '@/utils/request'\n\n\n"));
            String url = getUrl(m, annotatedElement);
            String fun = functionName.get(annotatedElement);
            boolean path = url.contains("{");
            String pathVar = "";
            if (path) {
                int b = url.indexOf("{");
                int e = url.indexOf("}");
                pathVar = url.substring(b + 1, e);
                url = url.replace("{", "${");
            }
            boolean requestBodyOrQuery = Arrays.stream(m.getParameters())
                    .anyMatch(p -> !p.isAnnotationPresent(PathVariable.class)
                            && !p.getType().isAssignableFrom(HttpServletRequest.class)
                            && !p.getType().isAssignableFrom(HttpServletResponse.class));

            boolean pageable = Arrays.stream(m.getParameters()).anyMatch(p -> p.getType().isAssignableFrom(Pageable.class));
            String data = " data ";
            String returnData = "data";
            if (pageable){
                if (annotatedElement.equals(PostMapping.class)){
                    fun = "httpPageablePost(%s%s)";
                }
                else if (annotatedElement.equals(GetMapping.class)){
                    fun = "httpPageableGet(%s%s)";
                }
                data = " data,total ";
                returnData = "{ data,total }";
            }
            Type genericReturnType1 = m.getGenericReturnType();
            if (genericReturnType1 instanceof ParameterizedType genericReturnType){
                Type returnArgs = genericReturnType.getActualTypeArguments()[0];
                if (returnArgs.equals(Void.class)){
                    data = " code ";
                    returnData = "code === 200";
                }
            }

            String funTemplate = String.format(fun, path ? "`" + url + "`" : "'" + url + "'", path ? requestBodyOrQuery ? ",params" : " " : ",params");
            String func = String.format(template,
                    m.getName().equals("delete")? "deleteById" : m.getName(),
                    path ? requestBodyOrQuery ? " " + pathVar + ":any, params?:any " : " " + pathVar + ":any " : " params?:any ",
                    data,
                    funTemplate,
                    returnData);
            sb.append(func).append("\n\n");
        }
    }


    private <T extends Annotation> String getUrl(Method m, Class<T> annotation) {

        Class<?> declaringClass = m.getDeclaringClass();
        Annotation an = m.getAnnotation(annotation);
        Map<String, Object> annotationAttributes = AnnotationUtils.getAnnotationAttributes(an);
        String baseUrl = ((String[]) annotationAttributes.get("value")).length> 0? ((String[]) annotationAttributes.get("value"))[0] :"";
        String url = StringUtils.isNotBlank(baseUrl)? StringUtils.startsWith(baseUrl, "/") ? baseUrl : "/" + baseUrl : baseUrl;
        if (declaringClass.isAnnotationPresent(RequestMapping.class)) {
            RequestMapping rq = declaringClass.getAnnotation(RequestMapping.class);
            String value = rq.value()[0];
            url = StringUtils.endsWith(value, "/") ? value.substring(0, value.length() - 1) + url : value + url;
        }
        return url;

    }


}
